package cn.com.bluemoon.daps.model.core.file.validate;

import cn.com.bluemoon.daps.common.domain.ResultBean;
import cn.com.bluemoon.daps.common.enums.DatabaseType;
import cn.com.bluemoon.daps.common.toolkit.BmAssetUtils;
import cn.com.bluemoon.daps.model.core.cache.ModelCacheHelper;
import cn.com.bluemoon.daps.model.core.file.*;
import cn.com.bluemoon.daps.model.core.file.parser.AbstractFileParser;
import cn.com.bluemoon.daps.model.core.file.report.ImportBaseInfo;
import cn.com.bluemoon.daps.model.entity.DapDataModel;
import cn.com.bluemoon.daps.model.entity.DapDataModelGroup;
import cn.com.bluemoon.daps.model.entity.DapDataModelTable;
import cn.com.bluemoon.daps.model.service.DapDataModelGroupService;
import cn.com.bluemoon.daps.model.service.DapDataModelTableService;
import cn.com.bluemoon.daps.standard.entity.DapStandardDataType;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static cn.com.bluemoon.daps.model.core.file.TableData.GROUP_NAME_SPLIT_CHAR;
import static cn.com.bluemoon.daps.model.core.file.parser.AbstractFileParser.PARSER_VALiD_ERROR_KEY;

/**
 * 多个数据关联系检查
 *
 * @author Jarod.Kong
 * @date 2021/3/11 14:14
 */
@Slf4j
public class DataRelationCheck {
    public static final String CHECK_VALID_ERROR_KEY = PARSER_VALiD_ERROR_KEY;

    /**
     * 获取模型下的表缓存
     *
     * @param modelId
     * @return
     */
    public static Set<String> getDbTableNameByModelId(String modelId) {
        DapDataModelTableService tableService = SpringUtil.getBean(DapDataModelTableService.class);
        LambdaQueryWrapper<DapDataModelTable> eq = tableService.bmLambdaQuery().eq(DapDataModelTable::getDataModelId, modelId)
                .select(DapDataModelTable::getEngName);
        return tableService.list(eq).stream().map(DapDataModelTable::getEngName).collect(Collectors.toSet());
    }

    /**
     * 针对数据集进行检查：多关联、查库、多sheet组合分析、异常结果整理
     *
     * @param tableInfoDtos 结果集
     * @param dataModel     对应的模型信息
     * @param dbType
     * @return 导入情况
     */
    @SuppressWarnings("rawtypes")
    public static ResultBean<List<IMetaData>> check(List<TableInfoDto> tableInfoDtos, DapDataModel dataModel, DatabaseType dbType) {
        // 拉取
        Set<String> dbTables = getDbTableNameByModelId(dataModel.getId());
        List<IMetaData> rowTableErrors = Lists.newArrayList();
        List<IMetaData> rowIndexErrors = Lists.newArrayList();
        List<IMetaData> rowFErrors = Lists.newArrayList();
        List<IMetaData> rowPErrors = Lists.newArrayList();
        for (TableInfoDto tableInfoDto : tableInfoDtos) {
            TableData tableData = tableInfoDto.getTableData();
            // 暂不改为策略模式
            // 表检查，判断表在模型是否已存 ...
            checkTableData(dataModel, dbTables, rowTableErrors, tableData);
            // 字段检查
            List<FieldData> fieldDatas = tableInfoDto.getFieldDatas();
            checkFieldData(dbType, rowFErrors, fieldDatas);
            // 索引检查 获取字段名称
            Set<String> fieldNames = fieldDatas.stream().map(FieldData::getFieldName).filter(StringUtils::isNotBlank)
                    .map(String::trim).collect(Collectors.toSet());
            List<IndexData> indexDatas = tableInfoDto.getIndexDatas();
            checkIndexData(rowIndexErrors, tableData, fieldNames, indexDatas);
            // 分区检查
            List<PartitionData> partitionDatas = tableInfoDto.getPartitionDatas();
            checkPartitionData(partitionDatas, rowPErrors, fieldDatas);
        }
        // 总结
        final List<ImportBaseInfo> baseInfos = Lists.newArrayList();
        // table
        if (!rowTableErrors.isEmpty()) {
            baseInfos.add(AbstractFileParser.getImportBaseInfo(tableInfoDtos.size(), rowTableErrors, "表", 0));
        }
        // index
        if (!rowIndexErrors.isEmpty()) {
            int fieldTotal = tableInfoDtos.stream().mapToInt(t -> t.getIndexDatas().size()).sum();
            baseInfos.add(AbstractFileParser.getImportBaseInfo(fieldTotal, rowIndexErrors, "索引", 2));
        }
        // index
        if (!rowPErrors.isEmpty()) {
            int fieldTotal = tableInfoDtos.stream().mapToInt(t -> t.getPartitionDatas().size()).sum();
            baseInfos.add(AbstractFileParser.getImportBaseInfo(fieldTotal, rowPErrors, "分区", 2));
        }
        // field
        if (!rowFErrors.isEmpty()) {
            int fieldTotal = tableInfoDtos.stream().mapToInt(t -> t.getFieldDatas().size()).sum();
            baseInfos.add(AbstractFileParser.getImportBaseInfo(fieldTotal, rowFErrors, "字段", 1));
        }
        if (!baseInfos.isEmpty()) {
            ResultBean<List<IMetaData>> error = ResultBean.error("文件中存在不合法问题，终止导入！");
            error.addMoreData(CHECK_VALID_ERROR_KEY, baseInfos);
            return error;
        }
        return ResultBean.ok(null);
    }

    private static void checkPartitionData(List<PartitionData> partitionDatas, List<IMetaData> rowPErrors, List<FieldData> fieldDatas) {
        if (partitionDatas == null || partitionDatas.isEmpty()) return;
        // 判断分区字段是否在字段中存在，预期值不可存在
        Set<String> fieldNames = fieldDatas.stream().map(FieldData::getFieldName).filter(StringUtils::isNotBlank).map(String::toLowerCase).collect(Collectors.toSet());
        for (PartitionData partitionData : partitionDatas) {
            String partitionFieldName = partitionData.getPartitionFieldName();
            if (fieldNames.contains(partitionFieldName.toLowerCase())){
                partitionData.setFail(true);
                log.error("分区字段{}不可在表{}字段列表中存在!!", partitionData, partitionData.getTableName());
                partitionData.getErrorMsgs().add("分区字段不可在表字段列表中存在！！");
            }
            if (partitionData.isFail()) {
                rowPErrors.add(partitionData);
            }
        }
    }

    private static void checkTableData(DapDataModel dataModel, Set<String> dbTables, List<IMetaData> rowTableErrors, TableData tableData) {
        if (tableData == null) return;
        if (dbTables.contains(tableData.getTableName())) {
            String errorMsg = StrUtil.format("系统模型已存在表英文名");
            tableData.setFail(true);
            tableData.getErrorMsgs().add(errorMsg);
        }
        String groupPathName = tableData.getGroupPathName();
        // 分组处理
        if (groupPathName != null) {
            String[] groupNames = groupPathName.split(GROUP_NAME_SPLIT_CHAR);
            boolean isDwSystem = dataModel.getSysType() == 2;
            if (groupNames.length > 2) {
                // error 1数仓系统最多指定两层分组  2非数仓系统最多指定一层分组
                String errorMsg = StrUtil.format("[组路径]{}数仓系统最多指定{}层分组", isDwSystem ? "" : "非", isDwSystem ? "两" : "一");
                log.debug("组路径为{}，错误信息：{}", groupPathName, errorMsg);
                tableData.setFail(true);
                tableData.getErrorMsgs().add(errorMsg);
            }
            String groupRootName = groupNames[0];
            String groupSonName = null;
            if (groupNames.length == 2) {
                groupSonName = groupNames[1];
            }
            String groupId = null;
            String groupPid = null;
            DapDataModelGroupService groupService = SpringUtil.getBean(DapDataModelGroupService.class);
            List<DapDataModelGroup> groups = groupService.findGroupByGroupNameAndModelIdCache(groupRootName, dataModel.getId());
            Optional<DapDataModelGroup> groupOpt = groups.stream().filter(grp -> grp.getName().equals(groupRootName)).findFirst();
            if (groupOpt.isPresent()) {
                DapDataModelGroup g = groupOpt.get();
                if (StringUtils.isBlank(groupSonName)) {
                    groupId = g.getId();
                    groupPid = g.getPid();
                } else {
                    List<DapDataModelGroup> groupSons = groupService.findGroupByGroupNameAndModelIdCache(groupSonName, dataModel.getId());
                    Optional<DapDataModelGroup> groupSonOpt = groupSons.stream().filter(grp -> grp.getPid().equals(g.getId())).findFirst();
                    if (groupSonOpt.isPresent()) {
                        DapDataModelGroup sonGroup = groupSonOpt.get();
                        groupId = sonGroup.getId();
                        groupPid = g.getId();
                    } else {
                        // 子组存在系统却找不到子组信息，报错
                        tableData.setFail(true);
                        String errorMsg = StrUtil.format("[组路径]指定的子组，在系统模型中无法找到！");
                        log.debug("组路径为{}，错误信息：{}", groupPathName, errorMsg);
                        tableData.getErrorMsgs().add(errorMsg);
                    }
                }
            } else {
                // 根组存在系统却找不到，报错
                String errorMsg = StrUtil.format("[组路径]指定的根组，在系统模型中无法找到！");
                log.debug("组路径为{}，错误信息：{}", groupPathName, errorMsg);
                tableData.setFail(true);
                tableData.getErrorMsgs().add(errorMsg);
            }
            // 提供转换后的分组给后续转换使用
            String groupIdStr = StringUtils.isBlank(groupPid) ? groupId : groupId + GROUP_NAME_SPLIT_CHAR + groupPid;
            tableData.setGroupPathName(groupIdStr);
        }
        if (tableData.isFail()) {
            rowTableErrors.add(tableData);
        }
    }

    private static void checkIndexData(List<IMetaData> rowIndexErrors, TableData tableData, Set<String> fieldNames, List<IndexData> indexDatas) {
        if (indexDatas == null) return;
        for (IndexData indexData : indexDatas) {
            indexData.getFieldNameNoBlank().forEach(f -> {
                if (!fieldNames.contains(f)) {
                    indexData.setFail(true);
                    String error = StrUtil.format("索引字段列表中的字段[{}]在字段列表中无法找到！", f);
                    log.debug("表{}-{}", tableData.getTableName(), error);
                    indexData.getErrorMsgs().add(error);
                }
            });
            if (indexData.isFail()) {
                rowIndexErrors.add(indexData);
            }
        }
    }

    private static void checkFieldData(DatabaseType dbType, List<IMetaData> rowFErrors, List<FieldData> fieldDatas) {
        if (fieldDatas == null) return;
        int fieldRank = 1;
        for (FieldData fieldData : fieldDatas) {
            // 增加排序排序
            fieldData.setFieldRank(fieldRank++);
            // 增加字段类型校验
            if (dbType != null) {
                Map<DatabaseType, CaseInsensitiveMap<String, DapStandardDataType>> dataTypeCache = ModelCacheHelper.findDbTypeAndCaseInsensitiveDataTypeCache();
                CaseInsensitiveMap<String, DapStandardDataType> codeAndDataType = dataTypeCache.get(dbType);
                // 不应该发生的，但是获取基础数据失败的情况下，不允许导入！！
                BmAssetUtils.isTrue(!dataTypeCache.isEmpty() && !codeAndDataType.isEmpty(), "请定义数据库"+dbType+"的字段类型基础数据！！");
                String filedDataType = fieldData.getFiledDataType();
                if (!codeAndDataType.containsKey(filedDataType)) {
                    // 不存在excel中的对应字段类型
                    String dtError = StrUtil.format("模型对应的数据库{}的非法字段类型[{}]", dbType, filedDataType);
                    log.error(dtError);
                    fieldData.getErrorMsgs().add(dtError);
                    fieldData.setFail(true);
                }
            }
            if (fieldData.isFail()) {
                rowFErrors.add(fieldData);
            }
        }
    }

}
